package com.quectel.core.module.parkingcard.service.impl;


import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.google.common.base.Preconditions;
import com.quectel.constant.core.car.CarConstants;
import com.quectel.constant.core.parking.CarParkingSpaceConstants;
import com.quectel.constant.core.parkingcard.ParkingCardMemberConstants;
import com.quectel.constant.core.parkingcard.ParkingCardOrderConstants;
import com.quectel.constant.core.parkingcard.ParkingCardOrderDeviceConstants;
import com.quectel.constant.global.SqlConstants;
import com.quectel.constant.global.SystemConstants;
import com.quectel.util.aspect.lock.annotation.Lock;
import com.quectel.core.constants.RedisCacheConstants;
import com.quectel.core.constants.RedisLockConstants;
import com.quectel.core.module.car.dto.CarDto;
import com.quectel.core.module.car.service.CarService;
import com.quectel.core.module.carparking.dto.CarParkingSpaceDto;
import com.quectel.core.module.carparking.service.CarParkingSpaceService;
import com.quectel.core.module.household.dto.HouseholdDto;
import com.quectel.core.module.household.service.HouseholdService;
import com.quectel.core.module.mobileuser.dto.MobileUserCarDto;
import com.quectel.core.module.mobileuser.service.MobileUserCarService;
import com.quectel.core.module.parking.dto.ParkingCurrentCarDto;
import com.quectel.core.module.parking.service.ParkingCurrentCarService;
import com.quectel.core.module.parkingcard.dao.ParkingCardOrderDao;
import com.quectel.core.module.parkingcard.dto.*;
import com.quectel.core.module.parkingcard.entity.ParkingCardOrderEntity;
import com.quectel.core.module.parkingcard.service.ParkingCardMemberService;
import com.quectel.core.module.parkingcard.service.ParkingCardOrderDeviceService;
import com.quectel.core.module.parkingcard.service.ParkingCardOrderService;
import com.quectel.core.module.village.service.VillageService;
import com.quectel.util.common.CopyUtils;
import com.quectel.util.common.JacksonUtils;
import com.quectel.util.ex.CheckErrorException;
import com.quectel.util.kit.CacheKit;
import com.quectel.util.kit.Snowflake;
import com.quectel.util.mybatis.MyBatisPlusUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author ryan
 * @email ryan.ran@quectel.com
 * @date 2023/04/18 17:23
 */
@DubboService
public class ParkingCardOrderServiceImpl implements ParkingCardOrderService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ParkingCardOrderService.class);

    /**
     * 入参解释:实体id
     */
    private static final Function<Long, String> PARKING_CARD_ORDER_CACHE_BY_ID_FUNC = id -> String.format(RedisCacheConstants.PROJECT_NAME + "PARKING_CARD_ORDER_CACHE_BY_ID:%d", id);

    /**
     * 清除缓存方法
     */
    private static final Consumer<ParkingCardOrderDto> CLEAR_CACHE_FUNC = dto -> {
        CacheKit.invalidRedisCache(PARKING_CARD_ORDER_CACHE_BY_ID_FUNC.apply(dto.getId()));
    };

    @Autowired
    private ParkingCardOrderDao parkingCardOrderDao;
    @Autowired
    private CarService carService;
    @Autowired
    private MobileUserCarService mobileUserCarService;
    @Autowired
    private HouseholdService householdService;
    @Autowired
    private ParkingCardMemberService parkingCardMemberService;
    @Autowired
    private ParkingCurrentCarService parkingCurrentCarService;
    @Autowired
    private VillageService villageService;
    @Autowired
    private CarParkingSpaceService parkingSpaceService;
    @Autowired
    private ParkingCardOrderDeviceService parkingCardOrderDeviceService;
    @Autowired
    private CarParkingSpaceService carParkingSpaceService;

    @Override
    public List<CountCardOrderByTimeDto> countOrderGroupByTime(Map<String, Object> params) {
        if (params.get(SqlConstants.DATE_FORMAT_TYPE_FILED) == null) {
            throw new CheckErrorException("时间格式化类型为必传参数");
        }
        return parkingCardOrderDao.countRecordGroupByTime(params);
    }

    @Override
    @Lock(
            lockExpresses = {
                    RedisLockConstants.PARKING_CARD_LOCK + "#{#parkingCardOrder.villageId}_#{#parkingCardOrder.license}_#{#parkingCardOrder.licenseColor}"
            }
    )
    @Transactional
    public Date approve(ParkingCardOrderDto parkingCardOrder) {
        ParkingCardOrderDto dbCarOrderDto = selectById(parkingCardOrder.getId());
        if (ParkingCardOrderConstants.OrderAuditStatusEnum.NO_AUDIT.getAuditStatus().equals(dbCarOrderDto.getAuditStatus())
                && ParkingCardOrderConstants.OrderPayStatusEnum.PAY_SUCCESS.getPayStatus().equals(dbCarOrderDto.getPayStatus())) {
            // 未审核并且已缴费的才审核

            final Date currentDate = new Date();

            // 更新车位状态
            CarParkingSpaceDto newlySpaceDto = new CarParkingSpaceDto();
            newlySpaceDto.setId(parkingCardOrder.getCarParkingSpaceId());
            newlySpaceDto.setState(CarParkingSpaceConstants.SpaceStateEnum.LEASED.getState());
            carParkingSpaceService.updateById(newlySpaceDto);

            Long carId = parkingCardOrder.getCarId();
            CarDto carDto = parkingCardOrder.getCar();
            if (ParkingCardOrderConstants.OrderIsRegistCarEnum.NO_REGIST.getRegistStatus().equals(parkingCardOrder.getIsRegistCar())) {
                // 车辆未注册，有可能来自居民端选择mobileUser车辆，有可能是cms首次添加车辆，也可能是其他小区的注册车辆

                Preconditions.checkNotNull(carDto);

                carDto.setTenantId(parkingCardOrder.getTenantId());
                carDto.setVillageId(parkingCardOrder.getVillageId());
                if (Objects.isNull(carDto.getCarType())) {
                    carDto.setCarType(CarConstants.CarTypeEnum.COMMON.getCarType());
                }
                // 补充车辆信息
                if (StringUtils.isBlank(carDto.getOwnerMobile()) || StringUtils.isBlank(carDto.getOwnerName())) {
                    HouseholdDto householdDto = householdService.selectById(parkingCardOrder.getHouseholdId());
                    Preconditions.checkNotNull(householdDto);
                    carDto.setOwnerName(householdDto.getName());
                    carDto.setOwnerMobile(householdDto.getMobile());
                }

                carId = carService.save(carDto);
            }

            ParkingCardOrderDto newlyDto = new ParkingCardOrderDto();
            newlyDto.setId(dbCarOrderDto.getId());
            newlyDto.setCarId(carId);
            newlyDto.setIsRegistCar(ParkingCardOrderConstants.OrderIsRegistCarEnum.IS_REGIST.getRegistStatus());
            newlyDto.setAuditStatus(ParkingCardOrderConstants.OrderAuditStatusEnum.AUDITED.getAuditStatus());
            newlyDto.setCarParkingSpaceId(parkingCardOrder.getCarParkingSpaceId());
            newlyDto.setAuditTime(currentDate);
            updateById(newlyDto);

            // 产生会员或续期，这里规定了一个截止日计算策略
            // 1.存在生效会员信息，直接续期，计算截止日的起始时间为上次会员失效日
            // 2.新办会员，在有在停车辆的情况下，计算截止日的起始时间为车辆入场时间，否则为当前时间
            // 3.过期会员，在有在停车辆的情况下，计算截止日的起始时间为max(上次会员失效日，车辆入场时间)，否则为当前时间

            ParkingCardMemberDto parkingCardMemberDto = parkingCardMemberService.selectOneMember(parkingCardOrder.getVillageId()
                    , parkingCardOrder.getLicense(), parkingCardOrder.getLicenseColor());

            Date startTime = null;
            if (Objects.nonNull(parkingCardMemberDto) && parkingCardMemberDto.isValid()) {
                startTime = parkingCardMemberDto.getDeadline();
            } else {
                // 查询在停车信息
                ParkingCurrentCarDto parkingCurrentCarDto = parkingCurrentCarService
                        .selectOneCar(parkingCardOrder.getVillageId(), parkingCardOrder.getLicense(), parkingCardOrder.getLicenseColor());

                if (Objects.nonNull(parkingCurrentCarDto)) {
                    long deadlineTimeMillis = Objects.isNull(parkingCardMemberDto) ? 0L : parkingCardMemberDto.getDeadline().getTime();
                    startTime = deadlineTimeMillis > parkingCurrentCarDto.getInTime().getTime()
                            ? new Date(deadlineTimeMillis) : parkingCurrentCarDto.getInTime();
                } else {
                    startTime = currentDate;
                }
            }

            // 计算截止日
            Integer month = ParkingCardOrderConstants.OrderCardTypeEnum
                    .getMonthByCardType(parkingCardOrder.getCardType());
            Preconditions.checkNotNull(month);
            DateTime newDeadline = DateUtil.offsetMonth(startTime, month);

            if (Objects.isNull(parkingCardMemberDto)) {
                // 新办
                ParkingCardMemberDto newMemberDto = new ParkingCardMemberDto();
                newMemberDto.setTenantId(parkingCardOrder.getTenantId());
                newMemberDto.setVillageId(parkingCardOrder.getVillageId());
                newMemberDto.setCarParkingSpaceId(parkingCardOrder.getCarParkingSpaceId());
                newMemberDto.setCarId(carId);
                newMemberDto.setMobile(carDto.getOwnerMobile());
                newMemberDto.setLicense(parkingCardOrder.getLicense());
                newMemberDto.setLicenseColor(parkingCardOrder.getLicenseColor());
                newMemberDto.setIsFreeze(ParkingCardMemberConstants.MemberIsFreezeEnum.NO_FREEZE.getFreezeStatus());
                newMemberDto.setCumulativeSavings(BigDecimal.ZERO);
                newMemberDto.setDeadline(newDeadline);
                newMemberDto.setJoinTime(currentDate);
                parkingCardMemberService.save(newMemberDto);
            } else {
                // 续卡
                ParkingCardMemberDto updateMemberDto = new ParkingCardMemberDto();
                updateMemberDto.setId(parkingCardMemberDto.getId());
                updateMemberDto.setDeadline(newDeadline);
                parkingCardMemberService.updateById(updateMemberDto);
            }

            return newDeadline;
        }

        return null;
    }

    @Override
    public ParkingCardOrderDto selectCacheById(Long id) {
        return CacheKit.cacheToRedis(
                () -> selectById(id),
                PARKING_CARD_ORDER_CACHE_BY_ID_FUNC.apply(id),
                SystemConstants.NOT_NULL_CACHE_EXPIRE_SECONDS
        );
    }

    @Override
    public ParkingCardOrderDto selectById(Long id) {
        ParkingCardOrderEntity entity = parkingCardOrderDao.selectById(id);
        return paddingField(CopyUtils.copyObj(entity, ParkingCardOrderDto.class));
    }

    @Override
    public List<ParkingCardOrderDto> queryList(Map<String, Object> params) {
        List<ParkingCardOrderEntity> list = parkingCardOrderDao.queryList(params);
        List<ParkingCardOrderDto> result = CopyUtils.copyList(list, ParkingCardOrderDto.class);
        result.forEach(this::paddingField);
        return result;
    }

    /**
     * 此方法慎用 禁止填充List一类 比如一个人有多个地址这里不允许填充
     * 填充的原则是: 1:被填充对象不经常变换 2:根据id可以走缓存 3数据足够简单 比如设备类型 所属园区等
     *
     * @param dto
     * @return
     */
    private ParkingCardOrderDto paddingField(ParkingCardOrderDto dto) {
        if (dto != null) {
            dto.setVillage(villageService.selectCacheById(dto.getVillageId()));
            dto.setParkingCard(JacksonUtils.parse(dto.getCardJson(), ParkingCardDto.class));
            dto.setCarParkingSpace(parkingSpaceService.selectCacheById(dto.getCarParkingSpaceId()));

            CarDto isCertifiedCar = carService.selectCacheById(dto.getCarId());
            if (Objects.nonNull(isCertifiedCar)) {
                dto.setCar(isCertifiedCar);
            } else {
                MobileUserCarDto mobileUserCarDto = mobileUserCarService.selectCacheById(dto.getCarId());
                if (Objects.nonNull(mobileUserCarDto)) {
                    CarDto carDto = CopyUtils.copyObj(mobileUserCarDto, CarDto.class);
                    carDto.setOwnerMobile(mobileUserCarDto.getMobileUser().getMobile());
                    carDto.setOwnerName(mobileUserCarDto.getMobileUser().getName());
                    dto.setCar(carDto);
                }
            }

            List<ParkingCardOrderDeviceDto> parkingCardOrderDeviceDtos = parkingCardOrderDeviceService.selectByOrderId(dto.getId());
            dto.setParkingCardOrderDeviceDetail(parkingCardOrderDeviceDtos);
            long count = parkingCardOrderDeviceDtos.parallelStream()
                    .filter(o -> !ParkingCardOrderDeviceConstants.StatusEnum.FAILURE.getStatus().equals(o.getStatus()))
                    .count();
            dto.setParkingCardOrderDeviceStatus(String.format("%s/%s", count, parkingCardOrderDeviceDtos.size()));
        }
        return dto;
    }

    @Override
    public int queryTotal(Map<String, Object> params) {
        return parkingCardOrderDao.queryTotal(params);
    }

    @Override
    public Long save(ParkingCardOrderDto dto) {
        ParkingCardOrderEntity entity = CopyUtils.copyObj(dto, ParkingCardOrderEntity.class);
        entity.setId(Snowflake.nextId());
        parkingCardOrderDao.insert(entity);
        return entity.getId();
    }

    @Override
    public void updateById(ParkingCardOrderDto dto) {
        ParkingCardOrderEntity entity = CopyUtils.copyObj(dto, ParkingCardOrderEntity.class);

        parkingCardOrderDao.updateById(entity);

        CLEAR_CACHE_FUNC.accept(dto);
    }

    @Override
    public void updateAllColumnById(ParkingCardOrderDto dto) {
        ParkingCardOrderEntity entity = CopyUtils.copyObj(dto, ParkingCardOrderEntity.class);

        MyBatisPlusUtils.updateAllColumnById(entity, parkingCardOrderDao);

        CLEAR_CACHE_FUNC.accept(dto);
    }

    @Override
    public void deleteById(Long id) {

        CLEAR_CACHE_FUNC.accept(selectById(id));

        parkingCardOrderDao.deleteById(id);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(Long[] ids) {
        for (Long id : ids) {
            deleteById(id);
        }
    }
}
